﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.Reflection;
using System.CodeDom.Compiler;
using System.IO;
using OrganizationName.Factory;
using OrganizationName.WebServices;
using System.Runtime.CompilerServices;
using OrganizationName.Runtime;

namespace OrganizationName.CodeGeneration
{
    public class CodeContext : ICodeContext
    {
        private const string DEBUG_SWITCH = " /d:DEBUG";
        private const string C_SHARP_LANGUAGE_IDENTIFIER = "c#";
        private const string PATH_SEPERATOR_CHAR = "/";
        private const string COMPILER_ERROR_FORMAT = "{0}\r\n\r\n";
        private const string ASSEMBLY_FILE_EXTENSION = ".dll";

        private CodeCompileUnit _CompileUnit;
        private CodeNamespace _NameSpace;
        private CodeTypeDeclaration _Class;
        private ICodeProvider _CodeProvider;
        private ICodeContext _ParentContext;
        private CodeStatementCollection _Statements;
        private Dictionary<string, System.Type> _DynamicallyLoadedTypes;
        private SortedList<string, Assembly> _LoadedAssemblies = new SortedList<string, Assembly>();
        private static DirectoryInfo _TemporaryDirectory = null;

        public virtual string FullPath
        {
            get
            {
                return (_ParentContext != null ? _ParentContext.FullPath + PATH_SEPERATOR_CHAR : string.Empty) + _CodeProvider.ToString();
            }
        }

        public virtual CodeCompileUnit CompileUnit
        {
            get
            {
                return IsRootContext ? _CompileUnit : _ParentContext.CompileUnit;
            }
        }

        public virtual CodeNamespace NameSpace
        {
            get
            {
                return IsRootContext ? _NameSpace : _ParentContext.NameSpace;
            }
        }

        public virtual CodeTypeDeclaration Class
        {
            get
            {
                return IsRootContext ? _Class : _ParentContext.Class;
            }
        }

        public virtual ICodeProvider CodeProvider
        {
            get
            {
                return _CodeProvider;
            }
        }

        public virtual ICodeProvider RootCodeProvider
        {
            get
            {
                return RootContext.CodeProvider;
            }
        }

        public virtual ICodeContext ParentContext
        {
            get
            {
                return _ParentContext;
            }
        }

        public virtual ICodeContext RootContext
        {
            get
            {
                return IsRootContext ? this : _ParentContext.RootContext;
            }
        }

        public virtual SortedList<string, Assembly> LoadedAssemblies
        {
            get
            {
                return IsRootContext ? _LoadedAssemblies : _ParentContext.LoadedAssemblies;
            }
        }

        public virtual bool IsRootContext
        {
            get
            {
                return _ParentContext == null;
            }
        }

        internal CodeContext(ICodeContext parentContext, ICodeProvider codeProvider, bool isStatementContainer)
        {
            _ParentContext = parentContext;
            _CodeProvider = codeProvider;
            if (isStatementContainer)
            {
                _Statements = new CodeStatementCollection();
            }
        }


        public virtual ICodeContext CreateChild(ICodeProvider codeProvider)
        {
            return CreateChild(codeProvider, false);
        }

        public virtual ICodeContext CreateChild(ICodeProvider codeProvider, bool isStatementContainer)
        {
            return new CodeContext(this, codeProvider, isStatementContainer);
        }

        public virtual CodeStatementCollection Statements
        {
            get
            {
                return _Statements != null ? _Statements : _ParentContext.Statements;
            }
        }

        public virtual Dictionary<string, System.Type> DynamicallyLoadedTypes
        {
            get
            {
                return _DynamicallyLoadedTypes != null ? _DynamicallyLoadedTypes : _ParentContext.DynamicallyLoadedTypes;
            }
        }

        internal CodeContext(ICodeProvider codeProvider, string typeNameToCompile)
        {
            _CompileUnit = new CodeCompileUnit();
            _NameSpace = new CodeNamespace(codeProvider.UniqueId);
            _CompileUnit.Namespaces.Add(_NameSpace);
            _Class = new CodeTypeDeclaration(typeNameToCompile);
            _NameSpace.Types.Add(_Class);
            _Class.Attributes = MemberAttributes.Public;
            _CodeProvider = codeProvider;
            _DynamicallyLoadedTypes = new Dictionary<string, System.Type>();
            _TemporaryDirectory = Directory.CreateDirectory(Path.GetTempPath() + "\\ReferencedAssemblies_" + DateTime.Now.Ticks);
        }

        public virtual void AddReference(System.Type typeToReference)
        {
            AddReference(typeToReference.Assembly);
        }

        public virtual void AddReference(Assembly assemblyToReference)
        {
            string codeBase = assemblyToReference.GetName().CodeBase;
            string localPath = new Uri(codeBase).LocalPath;

            if (!CompileUnit.ReferencedAssemblies.Contains(localPath))
            {
                LoaderOutput loaderOutput = Loader.Instance.Load(assemblyToReference);

                CompileUnit.ReferencedAssemblies.Add(localPath);
                foreach (Assembly referencedAssembly in loaderOutput.References)
                {
                    CompileUnit.ReferencedAssemblies.Add(new Uri(referencedAssembly.CodeBase).LocalPath);
                }
            }
        }

        public virtual Assembly Compile()
        {
            CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider(C_SHARP_LANGUAGE_IDENTIFIER);
            CodeGeneratorOptions codeGeneratorOptions = new CodeGeneratorOptions();
            CompilerParameters options = new CompilerParameters();

#if DEBUG
            using (StreamWriter codeWriter = File.CreateText(@"c:\temp\ox.cs"))
            {
                codeDomProvider.GenerateCodeFromCompileUnit(CompileUnit, codeWriter, codeGeneratorOptions);
            }
            options.IncludeDebugInformation = true;
            options.CompilerOptions += DEBUG_SWITCH;
#endif

            CompilerResults compilerResults = codeDomProvider.CompileAssemblyFromDom(options, _CompileUnit);
            if (compilerResults.Errors.HasErrors)
            {
                StringBuilder compilerErrorsBuilder = new StringBuilder();
                foreach (CompilerError compilerError in compilerResults.Errors)
                {
                    compilerErrorsBuilder.AppendFormat(COMPILER_ERROR_FORMAT, compilerError);
                }
                throw new CodeGenerationException(compilerErrorsBuilder.ToString(), this, null);
            }
            return compilerResults.CompiledAssembly;
        }
    }
}
